#include "BSPModel.h"
#include "handyFunctions.h"
#include "error.h"

// TODO: move it somewhere else
bool texturesVisible = true;
extern BSPModel level;

BSPModel::BSPModel()
{
	numberOfVertices	= 0;
	numberOfFaces		= 0;
	numberOfIndices		= 0;
	numberOfTextures	= 0;
	numberOfLightMaps	= 0;
	numberOfNodes		= 0;
	numberOfLeafs		= 0;
	numberOfLeafFaces	= 0;
	numberOfPlanes		= 0;

	vertices	= NULL;
	indices		= NULL;
	faces		= NULL;
	nodes		= NULL;
	leafs		= NULL;
	planes		= NULL;
	leafFaces	= NULL;
	memset(&clusters, 0, sizeof(BSPVisData));
}

void BSPModel::changeGamma(byte *image, int size, float factor)
{
	// go through every pixel in the lightmap
	for(int i = 0; i < size / 3; ++i, image += 3) 
	{
		float scale = 1.0f, temp = 0.0f;
		float r = 0, g = 0, b = 0;

		// extract the current RGB values
		r = (float)image[0];
		g = (float)image[1];
		b = (float)image[2];

		// multiply the factor by the RGB values, while keeping it to a 255 ratio
		r = r * factor / 255.0f;
		g = g * factor / 255.0f;
		b = b * factor / 255.0f;
		
		// check if the the values went past the highest value
		if(r > 1.0f && (temp = (1.0f/r)) < scale) scale = temp;
		if(g > 1.0f && (temp = (1.0f/g)) < scale) scale = temp;
		if(b > 1.0f && (temp = (1.0f/b)) < scale) scale = temp;

		// get the scale for this pixel and multiply it by our pixel values
		scale *= 255.0f;		
		r *= scale;	g *= scale;	b *= scale;

		// assign the new gamma'nized RGB values to image
		image[0] = (byte)r;
		image[1] = (byte)g;
		image[2] = (byte)b;
	}
}
//this function takes in the lightmap image bits and creates a texture map from them
void BSPModel::createLightmapTexture(UINT &texture, byte *imageBits, int width, int height)
{
	glGenTextures(1, &texture);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glBindTexture(GL_TEXTURE_2D, texture);
	changeGamma(imageBits, width * height * 3, gamma);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, imageBits);

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
void BSPModel::findTextureExtension(char *fileName)
{
	char JPGPath[MAX_PATH] = {0};
	char TGAPath[MAX_PATH] = {0};
	FILE *file = NULL;

	//TODO: getCurrentDirectory
	//getCurrentDirectory(MAX_PATH, JPGPath);
	//strcat(JPGPath, "/");
	strcat_s(JPGPath, MAX_PATH, fileName);
	strcpy_s(TGAPath, MAX_PATH, JPGPath);

	strcat_s(JPGPath, MAX_PATH, ".jpg");
	strcat_s(TGAPath, MAX_PATH, ".tga");

	if ((fopen_s(&file, JPGPath, "rb")) == 0)
	{
		strcat_s(fileName, MAX_PATH, ".jpg");
		return;
	}

	if ((fopen_s(&file, TGAPath, "rb")) == 0)
	{
		strcat_s(fileName, MAX_PATH, ".tga");
		return;
	}

	// TODO: add an error message if no texture found
}

bool BSPModel::loadBSP(const char *fileName)
{
	FILE *file = NULL;
	int i = 0;

	if((fopen_s(&file, fileName, "rb")) != 0)
	{
		Error::printError(FILE_NOT_FOUND, fileName);
		return false;
	}

	BSPHeader header = {0};
	BSPLump lumps[MAX_LUMPS] = {0};

	fread(&header, 1, sizeof(BSPHeader), file);
	fread(&lumps, MAX_LUMPS, sizeof(BSPLump), file);

	numberOfVertices = lumps[VERTICES].length / sizeof(BSPVertex);
	vertices = new BSPVertex[numberOfVertices];

	numberOfFaces = lumps[FACES].length / sizeof(BSPFace);
	faces = new BSPFace[numberOfFaces];

	numberOfIndices = lumps[INDICES].length / sizeof(int);
	indices = new int[numberOfIndices];

	numberOfTextures = lumps[TEXTURES].length / sizeof(BSPTexture);
	BSPTexture *tmpTextures = new BSPTexture[numberOfTextures];

	numberOfLightMaps = lumps[LIGHT_MAPS].length / sizeof(BSPLightMap);
	BSPLightMap *tempLightMaps = new BSPLightMap [numberOfLightMaps];

	fseek(file, lumps[VERTICES].offset, SEEK_SET);

	for (int i = 0; i < numberOfVertices; ++i)
	{
		fread(&vertices[i], 1, sizeof(BSPVertex), file);
		float temp = vertices[i].position[1];
		vertices[i].position[1] = vertices[i].position[2];
		vertices[i].position[2] = -temp;
	}

	fseek(file, lumps[INDICES].offset, SEEK_SET);
	fread(indices, numberOfIndices, sizeof(int), file);
	
	fseek(file, lumps[FACES].offset, SEEK_SET);
	fread(faces, numberOfFaces, sizeof(BSPFace), file);

	fseek(file, lumps[TEXTURES].offset, SEEK_SET);
	fread(tmpTextures, numberOfTextures, sizeof(BSPTexture), file);

	for (int i = 0; i < numberOfTextures; ++i)
	{
		findTextureExtension(tmpTextures[i].textureName);
		createTexture(textures[i], tmpTextures[i].textureName);
	}

	delete [] tmpTextures;

	fseek(file, lumps[LIGHT_MAPS].offset, SEEK_SET);

	for(i = 0; i < numberOfLightMaps ; i++)
	{
		fread(&tempLightMaps[i], 1, sizeof(BSPLightMap), file);
		createLightmapTexture(lightMaps[i], (unsigned char*)tempLightMaps[i].imageBits, 128, 128);
	}

	delete [] tempLightMaps;

	numberOfNodes = lumps[NODES].length / sizeof(BSPNode);
	nodes = new BSPNode[numberOfNodes];

	fseek(file, lumps[NODES].offset, SEEK_SET);
	fread(nodes, numberOfNodes, sizeof(BSPNode), file);

	numberOfLeafs = lumps[LEAFS].length / sizeof(BSPLeaf);
	leafs = new BSPLeaf[numberOfLeafs];

	fseek(file, lumps[LEAFS].offset, SEEK_SET);
	fread(leafs, numberOfLeafs, sizeof(BSPLeaf), file);

	// go through and convert all the leaf bounding boxes
	// to the normal OpenGL Y up axis
	for(i = 0; i < numberOfLeafs; i++)
	{
		int temp = leafs[i].min.y;
		leafs[i].min.y = leafs[i].min.z;
		leafs[i].min.z = -temp;

		temp = leafs[i].max.y;
		leafs[i].max.y = leafs[i].max.z;
		leafs[i].max.z = -temp;
	}

	numberOfLeafFaces = lumps[LEAF_FACES].length / sizeof(int);
	leafFaces = new int [numberOfLeafFaces];

	fseek(file, lumps[LEAF_FACES].offset, SEEK_SET);
	fread(leafFaces, numberOfLeafFaces, sizeof(int), file);

	numberOfPlanes = lumps[SPLITTING_PLANES].length / sizeof(BSPPlane);
	planes = new BSPPlane[numberOfPlanes];

	fseek(file, lumps[SPLITTING_PLANES].offset, SEEK_SET);
	fread(planes, numberOfPlanes, sizeof(BSPPlane), file);

	for(i = 0; i < numberOfPlanes; i++)
	{
		float temp = planes[i].normal[1];
		planes[i].normal[1] = planes[i].normal[2];
		planes[i].normal[2] = -temp;
	}

	fseek(file, lumps[VISIBILITY_DATA].offset, SEEK_SET);

	if(lumps[VISIBILITY_DATA].length) 
	{
		fread(&(clusters.numberOfClusters),	 1, sizeof(int), file);
		fread(&(clusters.bytesPerCluster), 1, sizeof(int), file);

		int size = clusters.numberOfClusters * clusters.bytesPerCluster;
		clusters.bitsets = new byte[size];

		fread(clusters.bitsets, 1, sizeof(byte) * size, file);
	}
	else
		clusters.bitsets = NULL;

	fclose(file);

	facesDrawn.resize(numberOfFaces);
	return true;
}

int BSPModel::findLeaf(const Vector &position)
{
	int i = 0;
	float distance = 0.0f;

	while(i >= 0)
	{
		const BSPNode &node = nodes[i];
		const BSPPlane &plane = planes[node.plane];

        distance =	plane.normal[0] * position[0] + 
					plane.normal[1] * position[1] + 
					plane.normal[2] * position[2] - plane.d;

        if(distance >= 0)	
		{
            i = node.front;
        }
        else		
		{
            i = node.back;
        }
    }

    return ~i;
}

inline int BSPModel::isClusterVisible(int current, int test)
{
	if(!clusters.bitsets || current < 0)
		return 1;

	byte visSet = clusters.bitsets[(current*clusters.bytesPerCluster) + (test / 8)];
	int result = visSet & (1 << ((test) & 7));

	return result;
}
void BSPModel::renderFace(int faceIndex)
{
	BSPFace *face = &faces[faceIndex];
	glVertexPointer(3, GL_FLOAT, sizeof(BSPVertex), &(vertices[face->startVertIndex].position));
	glEnableClientState(GL_VERTEX_ARRAY);

	glActiveTextureARB(GL_TEXTURE0_ARB);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glTexCoordPointer(2, GL_FLOAT, sizeof(BSPVertex), &(vertices[face->startVertIndex].textureCoordination));
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE0_ARB);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, textures[face->textureID]);

	//light maps
	glActiveTextureARB(GL_TEXTURE1_ARB);
	glClientActiveTextureARB(GL_TEXTURE1_ARB);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glClientActiveTextureARB(GL_TEXTURE1_ARB);
	glTexCoordPointer(2, GL_FLOAT, sizeof(BSPVertex), &(vertices[face->startVertIndex].lightMapCoordination));
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,  lightMaps[face->lightMapID]);

	glDrawElements(GL_TRIANGLES, face->numberOfIndices, GL_UNSIGNED_INT, &(indices[face->startIndex]));
}

void BSPModel::renderLevel(const Vector &position)
{
	facesDrawn.clearAll();

	int leafIndex = findLeaf(position);
	int cluster = leafs[leafIndex].cluster;
	int i = numberOfLeafs;
	visibleFaces = 0;

	while(i--)
	{
		BSPLeaf *leaf = &(leafs[i]);

		if(!isClusterVisible(cluster, leaf->cluster)) 
			continue;

		if(!frustum.boxInFrustum((float)leaf->min.x, (float)leaf->min.y, (float)leaf->min.z,
		  	 				     (float)leaf->max.x, (float)leaf->max.y, (float)leaf->max.z))
			continue;
		
		int faceCount = leaf->numberOfLeafFaces;

		while(faceCount--)
		{
			int faceIndex = leafFaces[leaf->leafFace + faceCount];

			if(faces[faceIndex].type != FACE_POLYGON)
				continue;

			if(!facesDrawn.on(faceIndex)) 
			{
				visibleFaces++;

				facesDrawn.set(faceIndex);
				renderFace(faceIndex);
			}
		}			
	}
}

void BSPModel::destroy()
{
	if (vertices)
	{
		delete [] vertices;
		vertices = NULL;
	}

	if (faces)
	{
		delete [] faces;
		faces = NULL;
	}

	if (indices)
	{
		delete [] indices;
		indices = NULL;
	}

	if(nodes)	
	{
		delete [] nodes;
		nodes = NULL;
	}

	if(leafs)	
	{
		delete [] leafs;
		leafs = NULL;
	}

	if(leafFaces)	
	{
		delete [] leafFaces;
		leafFaces = NULL;
	}

	if(planes)	
	{
		delete [] planes;
		planes = NULL;
	}

	if(clusters.bitsets)	
	{
		delete [] clusters.bitsets;
		clusters.bitsets = NULL;
	}

	if (textures != NULL)
		glDeleteTextures(numberOfTextures, textures);

	glDeleteTextures(numberOfLightMaps, lightMaps);
}

BSPModel::~BSPModel()
{
	destroy();
}